using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

namespace Common.Shared.Reflection {

	/// <summary>
	/// 
	/// </summary>
	public static class MethodInvoker {

		#region private fields
		private static Dictionary<IntPtr, int> retryCounts;
		#endregion

		#region constructor
		/// <summary>
		/// Initializes the <see cref="MethodInvoker"/> class.
		/// </summary>
		static MethodInvoker ( ) {
			retryCounts = new Dictionary<IntPtr, int> ( );
		}
		#endregion

		#region private methods
		/// <summary>
		/// Gets the method.
		/// </summary>
		/// <typeparam name="T">The object type</typeparam>
		/// <param name="obj">The object.</param>
		/// <param name="methodName">Name of the method.</param>
		/// <returns></returns>
		private static MethodInfo GetMethod<T> ( T obj, string methodName ) {
			return typeof ( T ).GetMethod ( methodName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase );
		}

		private static MethodInfo GetMethod ( object obj, string methodName ) {
			return obj.GetType().GetMethod ( methodName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase );
		}
		#endregion

		#region Invoke (object)
		/// <summary>
		/// Invokes the method.
		/// </summary>
		/// <param name="obj">The object.</param>
		/// <param name="methodName">Name of the method.</param>
		/// <returns>the method result</returns>
		/// <exception cref="System.MissingMethodException">throws if unknow method name is passed</exception>
		public static object SimpleInvoke ( object obj, string methodName ) {
			MethodInfo mi = GetMethod ( obj, methodName );
			if ( mi == null ) {
				throw new MissingMethodException ( obj.GetType ( ).FullName, methodName );
			}

			return SimpleInvoke ( obj, mi );
		}

		/// <summary>
		/// Invokes the method.
		/// </summary>
		/// <param name="obj">The object.</param>
		/// <param name="methodName">Name of the method.</param>
		/// <param name="parameters">The parameters.</param>
		/// <returns>the method result</returns>
		/// <exception cref="System.MissingMethodException">throws if unknow method name is passed</exception>
		public static object SimpleInvoke ( object obj, string methodName, object[] parameters ) {
			MethodInfo mi = GetMethod ( obj, methodName );
			if ( mi == null ) {
				throw new MissingMethodException ( obj.GetType().FullName, methodName );
			}

			return SimpleInvoke ( obj, mi, parameters );
		}

		/// <summary>
		/// Invokes the method.
		/// </summary>
		/// <param name="obj">The object.</param>
		/// <param name="mi">The method info.</param>
		/// <returns>the method result</returns>
		/// <exception cref="System.MissingMethodException">throws if unknow method name is passed</exception>
		public static object SimpleInvoke ( object obj, MethodInfo mi ) {
			return SimpleInvoke ( obj, mi, null );
		}

		/// <summary>
		/// Invokes the method.
		/// </summary>
		/// <param name="obj">The object.</param>
		/// <param name="mi">The method info.</param>
		/// <param name="parameters">The parameters.</param>
		/// <returns>The method result</returns>
		/// <exception cref="System.MissingMethodException">throws if unknow method name is passed</exception>
		public static object SimpleInvoke ( object obj, MethodInfo mi, object[] parameters ) {
			return Invoke<object, object> ( obj, mi, parameters );
		}
		#endregion

		#region InvokeWitRetry (object)
		/// <summary>
		/// Invokes the method with retry. If the method fails, it will retry the specified number of times
		/// </summary>
		/// <param name="obj">The object.</param>
		/// <param name="methodName">Name of the method.</param>
		/// <returns>The method result</returns>
		/// <exception cref="System.MissingMethodException">throws if unknow method name is passed</exception>
		public static object SimpleInvokeWithRetry ( object obj, string methodName ) {
			MethodInfo mi = GetMethod ( obj, methodName );
			if ( mi == null ) {
				throw new MissingMethodException ( obj.GetType ( ).FullName, methodName );
			}
			return InvokeWithRetry<object, object> ( obj, mi );
		}

		/// <summary>
		/// Invokes the method with retry. If the method fails, it will retry the specified number of times
		/// </summary>
		/// <param name="obj">The object.</param>
		/// <param name="methodName">Name of the method.</param>
		/// <param name="retryMaxCount">The retry max.</param>
		/// <returns>The method result</returns>
		/// <exception cref="System.MissingMethodException">throws if unknow method name is passed</exception>
		public static object SimpleInvokeWithRetry ( object obj, string methodName, int retryMaxCount ) {
			MethodInfo mi = GetMethod ( obj, methodName );
			if ( mi == null ) {
				throw new MissingMethodException ( obj.GetType ( ).FullName, methodName );
			}
			return InvokeWithRetry<object, object> ( obj, mi, retryMaxCount );
		}

		/// <summary>
		/// Invokes the method with retry. If the method fails, it will retry the specified number of times
		/// </summary>
		/// <param name="obj">The object.</param>
		/// <param name="methodName">Name of the method.</param>
		/// <param name="parameters">The parameters.</param>
		/// <param name="retryMaxCount">The retry max.</param>
		/// <returns>The method result</returns>
		/// <exception cref="System.MissingMethodException">throws if unknow method name is passed</exception>
		public static object SimpleInvokeWithRetry ( object obj, string methodName, object[] parameters, int retryMaxCount ) {
			MethodInfo mi = GetMethod ( obj, methodName );
			if ( mi == null ) {
				throw new MissingMethodException ( obj.GetType ( ).FullName, methodName );
			}
			return InvokeWithRetry<object, object> ( obj, mi, parameters, retryMaxCount );
		}

		/// <summary>
		/// Invokes the method with retry. If the method fails, it will retry the specified number of times
		/// </summary>
		/// <param name="obj">The object.</param>
		/// <param name="mi">The method info.</param>
		/// <returns>The method result</returns>
		/// <exception cref="System.MissingMethodException">throws if unknow method name is passed</exception>
		public static object SimpleInvokeWithRetry ( object obj, MethodInfo mi ) {
			return InvokeWithRetry<object, object> ( obj, mi );
		}

		/// <summary>
		/// Invokes the method with retry. If the method fails, it will retry the specified number of times
		/// </summary>
		/// <param name="obj">The object.</param>
		/// <param name="mi">The method info.</param>
		/// <param name="retryMaxCount">The retry max.</param>
		/// <returns>The method result</returns>
		/// <exception cref="System.MissingMethodException">throws if unknow method name is passed</exception>
		public static object SimpleInvokeWithRetry ( object obj, MethodInfo mi, int retryMaxCount ) {
			return InvokeWithRetry<object, object> ( obj, mi, retryMaxCount );
		}

		/// <summary>
		/// Invokes the method with retry. If the method fails, it will retry the specified number of times
		/// </summary>
		/// <param name="obj">The object.</param>
		/// <param name="mi">The method info.</param>
		/// <param name="parameters">The parameters.</param>
		/// <param name="retryMaxCount">The retry max.</param>
		/// <returns>The method result</returns>
		/// <exception cref="System.MissingMethodException">throws if unknow method name is passed</exception>
		public static object SimpleInvokeWithRetry ( object obj, MethodInfo mi, object[] parameters, int retryMaxCount ) {
			return InvokeWithRetry<object, object> ( obj, mi, parameters, retryMaxCount );
		}
		#endregion

		#region Invoke<T,R>

		/// <summary>
		/// Invokes the method.
		/// </summary>
		/// <typeparam name="T">The object type</typeparam>
		/// <typeparam name="R">The return type</typeparam>
		/// <param name="obj">The object.</param>
		/// <param name="methodName">Name of the method.</param>
		/// <returns>The result of the method</returns>
		/// <exception cref="System.MissingMethodException">throws if unknow method name is passed</exception>
		public static R Invoke<T, R> ( T obj, string methodName ) {
			return InvokeWithRetry<T, R> ( obj, methodName, null, 0 );
		}

		/// <summary>
		/// Invokes the method.
		/// </summary>
		/// <typeparam name="T">The object type</typeparam>
		/// <typeparam name="R">The return type</typeparam>
		/// <param name="obj">The object.</param>
		/// <param name="methodName">Name of the method.</param>
		/// <param name="parameters">The parameters.</param>
		/// <returns>The result of the method</returns>
		/// <exception cref="System.MissingMethodException">throws if unknow method name is passed</exception>
		public static R Invoke<T, R> ( T obj, string methodName, object[] parameters ) {
			MethodInfo mi = GetMethod<T> ( obj, methodName );
			if ( mi == null ) {
				throw new MissingMethodException ( typeof ( T ).FullName, methodName );
			}
			return InvokeWithRetry<T, R> ( obj, mi, parameters, 0 );
		}

		/// <summary>
		/// Invokes the method.
		/// </summary>
		/// <typeparam name="T">The object type</typeparam>
		/// <typeparam name="R">The return type</typeparam>
		/// <param name="obj">The object.</param>
		/// <param name="mi">The method info.</param>
		/// <returns>The result of the method</returns>
		/// <exception cref="System.MissingMethodException">throws if unknow method name is passed</exception>
		public static R Invoke<T, R> ( T obj, MethodInfo mi ) {
			return InvokeWithRetry<T, R> ( obj, mi, null, 0 );
		}

		/// <summary>
		/// Invokes the method.
		/// </summary>
		/// <typeparam name="T">The object type</typeparam>
		/// <typeparam name="R">The return type</typeparam>
		/// <param name="obj">The object.</param>
		/// <param name="mi">The method info.</param>
		/// <param name="parameters">The parameters.</param>
		/// <returns>The result of the method</returns>
		/// <exception cref="System.MissingMethodException">throws if unknow method name is passed</exception>
		public static R Invoke<T, R> ( T obj, MethodInfo mi, object[] parameters ) {
			return InvokeWithRetry<T, R> ( obj, mi, parameters, 0 );
		}
		#endregion

		#region Invoke<T>
		/// <summary>
		/// Invokes the method.
		/// </summary>
		/// <typeparam name="T">The object type</typeparam>
		/// <param name="obj">The object.</param>
		/// <param name="methodName">Name of the method.</param>
		/// <exception cref="System.MissingMethodException">throws if unknow method name is passed</exception>
		public static void Invoke<T> ( T obj, string methodName ) {
			InvokeWithRetry<T> ( obj, methodName, null, 0 );
		}

		/// <summary>
		/// Invokes the method.
		/// </summary>
		/// <typeparam name="T">The object type</typeparam>
		/// <param name="obj">The object.</param>
		/// <param name="methodName">Name of the method.</param>
		/// <param name="parameters">The parameters.</param>
		/// <exception cref="System.MissingMethodException">throws if unknow method name is passed</exception>
		public static void Invoke<T> ( T obj, string methodName, object[] parameters ) {
			MethodInfo mi = GetMethod<T> ( obj, methodName );
			if ( mi == null ) {
				throw new MissingMethodException ( typeof ( T ).FullName, methodName );
			}
			InvokeWithRetry<T> ( obj, mi, parameters, 0 );
		}

		/// <summary>
		/// Invokes the method.
		/// </summary>
		/// <typeparam name="T">The object type</typeparam>
		/// <param name="obj">The object.</param>
		/// <param name="mi">The method info.</param>
		/// <exception cref="System.MissingMethodException">throws if unknow method name is passed</exception>
		public static void Invoke<T> ( T obj, MethodInfo mi ) {
			InvokeWithRetry<T> ( obj, mi, null, 0 );
		}

		#endregion

		#region InvokeWithRetry<T,R>

		/// <summary>
		/// Invokes the method with retry. If the method fails, it will retry the specified number of times
		/// </summary>
		/// <typeparam name="T">Object type</typeparam>
		/// <typeparam name="R">Return Type</typeparam>
		/// <param name="obj">The object.</param>
		/// <param name="methodName">Name of the method.</param>
		/// <returns>The method result</returns>
		/// <exception cref="System.MissingMethodException">throws if unknow method name is passed</exception>
		public static R InvokeWithRetry<T, R> ( T obj, string methodName ) {
			return InvokeWithRetry<T, R> ( obj, methodName, null, 5 );
		}

		/// <summary>
		/// Invokes the method with retry. If the method fails, it will retry the specified number of times
		/// </summary>
		/// <typeparam name="T">Object type</typeparam>
		/// <typeparam name="R">Return Type</typeparam>
		/// <param name="obj">The object.</param>
		/// <param name="methodName">Name of the method.</param>
		/// <param name="retryMaxCount">The retry max.</param>
		/// <returns>The method result</returns>
		/// <exception cref="System.MissingMethodException">throws if unknow method name is passed</exception>
		public static R InvokeWithRetry<T, R> ( T obj, string methodName, int retryMaxCount ) {
			return InvokeWithRetry<T, R> ( obj, methodName, null, retryMaxCount );
		}

		/// <summary>
		/// Invokes the method with retry. If the method fails, it will retry the specified number of times
		/// </summary>
		/// <typeparam name="T">Object type</typeparam>
		/// <typeparam name="R">Return Type</typeparam>
		/// <param name="obj">The object.</param>
		/// <param name="methodName">Name of the method.</param>
		/// <param name="parameters">The parameters.</param>
		/// <param name="retryMaxCount">The retry max.</param>
		/// <returns>The method result</returns>
		/// <exception cref="System.MissingMethodException">throws if unknow method name is passed</exception>
		public static R InvokeWithRetry<T, R> ( T obj, string methodName, object[] parameters, int retryMaxCount ) {
			MethodInfo mi = GetMethod<T> ( obj, methodName );
			if ( mi == null ) {
				throw new MissingMethodException ( typeof ( T ).FullName, methodName );
			}
			return InvokeWithRetry<T, R> ( obj, mi, parameters, retryMaxCount );
		}

		/// <summary>
		/// Invokes the method with retry. If the method fails, it will retry the specified number of times
		/// </summary>
		/// <typeparam name="T">Object type</typeparam>
		/// <typeparam name="R">Return Type</typeparam>
		/// <param name="obj">The object.</param>
		/// <param name="mi">The method info.</param>
		/// <returns>The method result</returns>
		/// <exception cref="System.MissingMethodException">throws if unknow method name is passed</exception>
		public static R InvokeWithRetry<T, R> ( T obj, MethodInfo mi ) {
			return InvokeWithRetry<T, R> ( obj, mi, null, 5 );
		}

		/// <summary>
		/// Invokes the method with retry. If the method fails, it will retry the specified number of times
		/// </summary>
		/// <typeparam name="T">Object type</typeparam>
		/// <typeparam name="R">Return Type</typeparam>
		/// <param name="obj">The object.</param>
		/// <param name="mi">The method info.</param>
		/// <param name="retryMaxCount">The retry max.</param>
		/// <returns>The method result</returns>
		/// <exception cref="System.MissingMethodException">throws if unknow method name is passed</exception>
		public static R InvokeWithRetry<T, R> ( T obj, MethodInfo mi, int retryMaxCount ) {
			return InvokeWithRetry<T, R> ( obj, mi, null, retryMaxCount );
		}

		/// <summary>
		/// Invokes the method with retry. If the method fails, it will retry the specified number of times
		/// </summary>
		/// <typeparam name="T">Object type</typeparam>
		/// <typeparam name="R">Return Type</typeparam>
		/// <param name="obj">The object.</param>
		/// <param name="mi">The method info.</param>
		/// <param name="parameters">The parameters.</param>
		/// <param name="retryMaxCount">The max retry attempts.</param>
		/// <returns>The method result</returns>
		/// <exception cref="System.MissingMethodException">throws if unknow method name is passed</exception>
		public static R InvokeWithRetry<T, R> ( T obj, MethodInfo mi, object[] parameters, int retryMaxCount ) {

			if ( parameters == null ) {
				parameters = new object[] { };
			}
			if ( !retryCounts.ContainsKey ( mi.MethodHandle.Value ) ) {
				retryCounts.Add ( mi.MethodHandle.Value, 0 );
			}

			R ret = default ( R );
			try {
				ret = (R)mi.Invoke ( obj, parameters );
			} catch ( TargetInvocationException tiex ) {
				// check that we have an exception
				if ( tiex.InnerException != null ) {
					if ( retryCounts[ mi.MethodHandle.Value ] < retryMaxCount ) {
						retryCounts[ mi.MethodHandle.Value ]++;
						ret = InvokeWithRetry<T, R> ( obj, mi, parameters, retryMaxCount );
					} else {
						retryCounts.Remove ( mi.MethodHandle.Value );
						throw tiex.InnerException;
					}
				} else {
					retryCounts.Remove ( mi.MethodHandle.Value );
					throw;
				}
			} catch ( Exception ) {
				retryCounts.Remove ( mi.MethodHandle.Value );
				throw;
			}
			retryCounts.Remove ( mi.MethodHandle.Value );
			return ret;
		}

		#endregion

		#region InvokeWithRetry<T>

		/// <summary>
		/// Invokes the method with retry. If the method fails, it will retry the specified number of times
		/// </summary>
		/// <typeparam name="T">Object type</typeparam>
		/// <param name="obj">The object.</param>
		/// <param name="methodName">Name of the method.</param>
		public static void InvokeWithRetry<T> ( T obj, string methodName ) {
			InvokeWithRetry<T> ( obj, methodName, null, 5 );
		}

		/// <summary>
		/// Invokes the method with retry. If the method fails, it will retry the specified number of times
		/// </summary>
		/// <typeparam name="T">Object type</typeparam>
		/// <param name="obj">The object.</param>
		/// <param name="methodName">Name of the method.</param>
		/// <param name="retryMaxCount">The retry max.</param>
		public static void InvokeWithRetry<T> ( T obj, string methodName, int retryMaxCount ) {
			InvokeWithRetry<T> ( obj, methodName, null, retryMaxCount );
		}

		/// <summary>
		/// Invokes the method with retry. If the method fails, it will retry the specified number of times
		/// </summary>
		/// <typeparam name="T">Object type</typeparam>
		/// <param name="obj">The object.</param>
		/// <param name="methodName">Name of the method.</param>
		/// <param name="parameters">The parameters.</param>
		/// <param name="retryMaxCount">The retry max.</param>
		public static void InvokeWithRetry<T> ( T obj, string methodName, object[] parameters, int retryMaxCount ) {
			MethodInfo mi = GetMethod<T> ( obj, methodName );
			if ( mi == null ) {
				throw new MissingMethodException ( typeof ( T ).FullName, methodName );
			}
			InvokeWithRetry<T> ( obj, mi, parameters, retryMaxCount );
		}

		/// <summary>
		/// Invokes the method with retry. If the method fails, it will retry the specified number of times
		/// </summary>
		/// <typeparam name="T">Object type</typeparam>
		/// <param name="obj">The object.</param>
		/// <param name="mi">The method info.</param>
		public static void InvokeWithRetry<T> ( T obj, MethodInfo mi ) {
			InvokeWithRetry<T> ( obj, mi, null, 5 );
		}

		/// <summary>
		/// Invokes the method with retry. If the method fails, it will retry the specified number of times
		/// </summary>
		/// <typeparam name="T">Object type</typeparam>
		/// <param name="obj">The object.</param>
		/// <param name="mi">The method info.</param>
		/// <param name="retryMaxCount">The retry max.</param>
		public static void InvokeWithRetry<T> ( T obj, MethodInfo mi, int retryMaxCount ) {
			InvokeWithRetry<T> ( obj, mi, null, retryMaxCount );
		}

		/// <summary>
		/// Invokes the method with retry. If the method fails, it will retry the specified number of times
		/// </summary>
		/// <typeparam name="T">Object type</typeparam>
		/// <param name="obj">The object.</param>
		/// <param name="mi">The method info.</param>
		/// <param name="parameters">The parameters.</param>
		/// <param name="retryMaxCount">The retry max.</param>
		public static void InvokeWithRetry<T> ( T obj, MethodInfo mi, object[] parameters, int retryMaxCount ) {
			if ( parameters == null ) {
				parameters = new object[] { };
			}
			if ( !retryCounts.ContainsKey ( mi.MethodHandle.Value ) ) {
				retryCounts.Add ( mi.MethodHandle.Value, 0 );
			}
			try {
				mi.Invoke ( obj, parameters );
			} catch ( TargetInvocationException tiex ) {
				if ( tiex.InnerException != null ) {
					if ( retryCounts[ mi.MethodHandle.Value ] < retryMaxCount ) {
						retryCounts[ mi.MethodHandle.Value ]++;
						InvokeWithRetry<T> ( obj, mi, parameters, retryMaxCount );
					} else {
						retryCounts.Remove ( mi.MethodHandle.Value );
						throw tiex.InnerException;
					}
				} else {
					retryCounts.Remove ( mi.MethodHandle.Value );
					throw;
				}
			} catch ( Exception ) {
				retryCounts.Remove ( mi.MethodHandle.Value );
				throw;
			}
			retryCounts.Remove ( mi.MethodHandle.Value );
		}
		#endregion

	}
}
